package com.wmada.toolkit.rest;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.wmada.toolkit.common.DateFormat;
import com.wmada.toolkit.common.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BufferedHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.CharArrayBuffer;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.Cookie;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

/**
 * Description: 封装httpClient响应结果
 *
 * @author will.ma
 * @date 2019-4-25
 */
@SuppressWarnings("unused")
public class Response {

    private static final Logger LOGGER = LoggerFactory.getLogger(Response.class);

    /**
     * 响应状态码
     */
    private int statusCode;

    private String message;

    private HttpRequestBase request;
    private Header[]        headers;
    private byte[]          bytes;

    public Response() {
    }

    public Response(HttpRequestBase request, HttpResponse response) {
        this.request = request;
        this.statusCode = response.getStatusLine().getStatusCode();
        this.message = response.getStatusLine().getReasonPhrase();
        if (response.getAllHeaders() != null) {
            this.headers = response.getAllHeaders();
        } else {
            this.headers = new Header[0];
        }

        try {
            if (response.getEntity() != null) {
                this.bytes = EntityUtils.toByteArray(response.getEntity());
            } else {
                this.bytes = new byte[0];
            }
        } catch (IOException e) {
            LOGGER.warn("Error Reading Content.", e);
            this.bytes = new byte[0];
        }
    }

    Response(HttpRequestBase request, int statusCode, String message) {
        this.request = request;
        this.statusCode = statusCode;
        this.message = message;
        this.headers = new Header[0];
        this.bytes = new byte[0];
    }

    public int getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public HttpRequestBase getRequest() {
        return request;
    }

    public void setRequest(HttpRequestBase request) {
        this.request = request;
    }

    public Header[] getHeaders() {
        return headers;
    }

    public void setHeaders(Header[] headers) {
        this.headers = headers;
    }

    public byte[] getBytes() {
        return bytes;
    }

    public void setBytes(byte[] bytes) {
        this.bytes = bytes;
    }

    public String getString() {
        return getString(getCharset());
    }

    public String getString(String charset) {
        try {
            if (charset != null) {
                return new String(bytes, charset);
            } else {
                return new String(bytes);
            }
        } catch (IOException e) {
            LOGGER.warn("Parsing bytes Error.", e);
        }
        return null;
    }

    public <T> T getBean(Class<T> tClass) {
        return JSON.parseObject(getString(), tClass);
    }

    public <T> T getBean(TypeReference<T> type) {
        return JSON.parseObject(getString(), type);
    }

    public JSONObject getJSON() {
        return JSON.parseObject(getString());
    }

    public boolean isOk() {
        return statusCode == HttpStatus.SC_OK;
    }

    public List<Header> getHeaders(String name) {
        List<Header> values = new ArrayList<>();
        if (headers != null) {
            for (Header header : headers) {
                if (header.getName().equalsIgnoreCase(name)) {
                    values.add(header);
                }
            }
        }
        return values;
    }

    public Header getHeader(String name) {
        if (headers != null) {
            for (Header header : headers) {
                if (header.getName().equalsIgnoreCase(name)) {
                    return header;
                }
            }
        }
        return null;
    }

    public List<String> getHeaderValues(String name) {
        List<String> values = new ArrayList<>();
        if (headers != null) {
            for (Header header : headers) {
                if (header.getName().equalsIgnoreCase(name)) {
                    values.add(header.getValue());
                }
            }
        }
        return values;
    }

    public String getHeaderValue(String name) {
        if (headers != null) {
            for (Header header : headers) {
                if (header.getName().equalsIgnoreCase(name)) {
                    return header.getValue();
                }
            }
        }
        return null;
    }

    public LinkedHashMap<String, String> getCookies() {
        LinkedHashMap<String, String> cookies = new LinkedHashMap<>();

        List<Cookie> requestCookies = parseRequestCookies(request.getHeaders("cookie"));
        for (Cookie cookie : requestCookies) {
            cookies.put(cookie.getName(), cookie.getValue());
        }

        for (Header header : getHeaders("Set-Cookie")) {
            Cookie cookie = parseResponseCookie(header);
            if (cookie != null) {
                if (cookie.getMaxAge() == 0) {
                    cookies.remove(cookie.getName());
                } else {
                    cookies.put(cookie.getName(), cookie.getValue());
                }
            }
        }
        return cookies;
    }

    private Cookie parseResponseCookie(Header header) {
        // 使用默认的Header中，getValue方法，会将payforreadsn=EXPIRED; Path=/; Expires=Thu, 05-Nov-2020 11:13:15 GMT; HttpOnly从逗号处截断。
        String headerValue;
        if (header instanceof BufferedHeader) {
            CharArrayBuffer buffer = ((BufferedHeader) header).getBuffer();
            headerValue = buffer.substring(((BufferedHeader) header).getValuePos(), buffer.length());
        } else {
            headerValue = header.getValue();
        }
        String[] pairs = headerValue.split(";");
        if (pairs.length == 0) {
            return null;
        }
        String   root = pairs[0];
        String[] kv   = root.split("=", 2);
        Cookie   cookie;
        if (kv.length == 1) {
            cookie = new Cookie(kv[0].trim(), "");
        } else {
            cookie = new Cookie(kv[0].trim(), kv[1].trim());
        }

        for (int i = 1; i < pairs.length; i++) {
            String[] element = pairs[i].split("=", 2);
            if (element.length == 0) {
                continue;
            }

            String key = element[0].trim();
            String val = element.length == 2 ? element[1].trim() : "";
            switch (key.trim().toLowerCase()) {
                case "comment":
                    cookie.setComment(val);
                    break;
                case "domain":
                    cookie.setDomain(val);
                    break;
                case "max-age":
                    cookie.setMaxAge(Integer.parseInt(val));
                    break;
                case "path":
                    cookie.setPath(val);
                    break;
                case "secure":
                    cookie.setSecure(true);
                    break;
                case "version":
                    cookie.setVersion(Integer.parseInt(val));
                    break;
                case "httponly":
                    cookie.setHttpOnly(true);
                    break;
                case "expires":
                    DateFormat format = new DateFormat("EEE, dd-MMM-yyyy HH:mm:ss 'GMT'", Locale.ENGLISH);
                    Date date = format.parse(val, null);
                    if (date != null) {
                        long expire = date.getTime() - System.currentTimeMillis();
                        if (expire <= 0) {
                            cookie.setMaxAge(0);
                        } else {
                            cookie.setMaxAge((int) (expire / 1000));
                        }
                    }
                default:
                    // Do nothing.
            }
        }
        return cookie;
    }

    private List<Cookie> parseRequestCookies(Header[] headers) {
        List<Cookie> cookies = new ArrayList<>();
        for (Header header : headers) {
            for (HeaderElement element : header.getElements()) {
                cookies.add(new Cookie(element.getName(), element.getValue()));
                for (NameValuePair parameter : element.getParameters()) {
                    cookies.add(new Cookie(parameter.getName(), parameter.getValue()));
                }
            }
        }
        return cookies;
    }

    public String getContentType() {
        return getHeader(HTTP.CONTENT_TYPE, null, null);
    }

    public String getCharset() {
        return getHeader(HTTP.CONTENT_TYPE, null, "charset");
    }

    public String getHeader(String headerName, String elementName, String parameterName) {
        Header header = getHeader(headerName);
        if (header == null) {
            return null;
        }
        HeaderElement[] elements = header.getElements();
        if (elements.length == 0) {
            return null;
        }
        HeaderElement e = null;
        if (elementName == null) {
            e = elements[0];
        } else {
            for (HeaderElement element : elements) {
                if (elementName.equalsIgnoreCase(element.getName())) {
                    e = element;
                    break;
                }
            }
        }
        if (e == null) {
            return null;
        }
        if (parameterName == null) {
            return e.getName();
        }
        NameValuePair pair = e.getParameterByName(parameterName);
        if (pair != null) {
            return pair.getValue();
        } else {
            return null;
        }
    }

    public File save(String path) {
        return save(path, getFileName(), true);
    }

    public File save(Path path) {
        return save(path, getFileName(), true);
    }

    public File save(String path, String filename) {
        return save(path, filename, true);
    }

    public File save(Path path, String filename) {
        return save(path, filename, true);
    }

    public File save(String path, String filename, boolean detectSuffix) {
        if (detectSuffix) {
            filename = detectExtension(filename);
        }
        File file = Paths.get(path, filename).toFile();
        if (FileUtils.isWritable(file)) {
            if (FileUtils.writeBytes(file, bytes)) {
                return file;
            }
        } else {
            LOGGER.warn(file.getAbsolutePath() + " Cannot Write.");
        }
        return null;
    }

    public File save(Path path, String filename, boolean detectSuffix) {
        if (detectSuffix) {
            filename = detectExtension(filename);
        }
        File file = path.resolve(filename).toFile();
        if (FileUtils.isWritable(file)) {
            if (FileUtils.writeBytes(file, bytes)) {
                return file;
            }
        } else {
            LOGGER.warn(file.getAbsolutePath() + " Cannot Write.");
        }
        return null;
    }

    private String getFileName() {
        String filename = getHeader("Content-Disposition", null, "filename");
        if (StringUtils.isNotBlank(filename)) {
            return filename;
        }
        if (request != null) {
            try {
                filename = request.getURI().toURL().getFile();
                if (StringUtils.isNotBlank(filename)) {
                    while (filename.endsWith("/")) {
                        filename = filename.substring(0, filename.length() - 1);
                    }
                    filename = filename.lastIndexOf('/') >= 0 ? filename.substring(filename.lastIndexOf('/') + 1) : filename;
                    filename = filename.replaceAll("[\\\\/:*?\"<>|]", "");
                    return filename.length() < 120 ? filename : filename.substring(0, 120);
                }
            } catch (MalformedURLException e) {
                LOGGER.warn("Malformed Url.", e);
            }
        }
        return null;
    }

    private String detectExtension(String filename) {
        MimeType mimeType = MimeType.getMimeTypeByMime(getHeaderValue("Content-Type"));
        if (mimeType != null) {
            String ext = FileUtils.getFileSuffix(filename);
            if (mimeType.getExtension().equalsIgnoreCase(ext)) {
                return filename;
            } else {
                return filename + "." + mimeType.getExtension();
            }
        } else {
            return filename;
        }
    }

    public String printAllInfo() {
        return printRequest() + "\n" + printResponse();
    }

    public String printRequest() {
        StringBuilder sb = new StringBuilder();
        sb.append("======================== HTTP Request ========================").append('\n');
        sb.append("URL: ").append(request.getURI()).append('\n');
        sb.append("Method: ").append(request.getMethod()).append('\n');
        sb.append("Version: ").append(request.getProtocolVersion()).append('\n');
        sb.append("--------------------- HTTP Request  HEAD ---------------------").append('\n');
        if (request.getAllHeaders() != null) {
            for (Header header : request.getAllHeaders()) {
                sb.append(header).append('\n');
            }
        }
        if (request instanceof HttpEntityEnclosingRequest) {
            sb.append("--------------------- HTTP Request  BODY ---------------------").append('\n');
            HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
            if (entity instanceof StringEntity) {
                sb.append(entity).append('\n').append(readEntityAsString(entity)).append('\n');
            } else {
                sb.append(entity).append('\n');
            }
        }
        return sb.toString();
    }

    private String readEntityAsString(HttpEntity entity) {
        Charset charset = ContentType.getOrDefault(entity).getCharset();
        return new String(readBytes(entity), charset);
    }

    private byte[] readBytes(HttpEntity entity) {
        try (ByteArrayOutputStream bytesOut = new ByteArrayOutputStream()) {
            entity.writeTo(bytesOut);
            return bytesOut.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public String printResponse() {
        StringBuilder sb = new StringBuilder();
        sb.append("======================== HTTP Response ========================").append('\n');
        sb.append(statusCode).append(" ").append(message).append('\n');
        sb.append(printResponseHead()).append(printResponseBody());
        return sb.toString();
    }

    public String printResponseHead() {
        StringBuilder sb = new StringBuilder();
        sb.append("--------------------- HTTP Response HEAD ---------------------").append('\n');
        for (Header header : headers) {
            sb.append(header.toString()).append('\n');
        }
        return sb.toString();
    }

    public String printResponseBody() {
        StringBuilder sb = new StringBuilder();
        if (bytes != null && bytes.length > 0) {
            sb.append("--------------------- HTTP Response BODY ---------------------").append('\n');
            sb.append(getString()).append('\n');
        }
        return sb.toString();
    }

}
